/***
 * this file is part of "flashheart" sofware program
 * 
 * it is hereby placed in the PUBLIC DOMAIN by its author
 * 
 * there is NO WARRANTY, whether express or implied
 * 
 * by using this software, you agree to assume any and all risks of
 * such use, and that, to the maximum extent permitted by law, neither
 * the author of this software (the "author"), nor the party that
 * supplied this software to you (the "supplier"), is responsible for
 * any damage or loss caused to you or to any third party, even if
 * said damage or loss is directly attributable to your use of this
 * software, and even if the possibility of said damage or loss was
 * known to or reasonably forseeable by the author or the supplier or
 * both.
 ***/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <mpsse.h>

#include <time.h>

#define assure(what_expr)			\
  if (!(what_expr)) { assure_failed(__FILE__, __LINE__); } else { }

static void assure_failed(char const * file, int line)
{
  fprintf(stderr, "fail: (%s) [%d]\n", file, line);
  exit(1);
  while (1);
}

struct mpsse_context* frob;
char frob_byte;

#define ENTER() assure((Start (frob)) == MPSSE_OK)
#define LEAVE() assure((Stop  (frob)) == MPSSE_OK)

#define TX(what_valu)     assure((FastWrite (frob, ((frob_byte = (what_valu)), &frob_byte) , 1) == MPSSE_OK));
#define RX()          (({ assure((FastRead  (frob,                             &frob_byte  , 1) == MPSSE_OK)); }), frob_byte)

#define RXIU() (((int)(RX())) & 0xFF)

static int status(int cmd)
{
  ENTER();
  TX(cmd);
  int rv = RXIU();
  LEAVE();
  return rv;
}

static int status1(void)
{
  return status(0x05);
}

static void writeenable(void)
{
  ENTER();
  TX(0x06);
  LEAVE();
}

static void waitready(void)
{
  int i = 0;
  
  while (((status1() & 0x1) == 0x1)) {
    if ((i++) == 0) {
      fprintf(stderr, "waitready (busy) ...\n");
    }
  }
  
  if (i != 0) {
    fprintf(stderr, "waitready completed after %d polling iteration(s)\n", i);
  }
}

static void chiperase(void)
{
  writeenable();
  
  ENTER();
  TX(0x60);
  LEAVE();

  waitready();
}

static int allfs(char const * buf, int len)
{
  int x = 0xFF;
  
  for (int i = 0; i < len; i++) {
    x &= (((int)(buf[i])) & 0xFF);
  }
  
  return (x == 0xFF);
}

static char const * narg(int* argc, char const *const ** argv)
{
  assure(((*argc)--) > 0);
  return (*((*argv)++));
}

#define NARG() narg(&argc, &argv)

int main(int argc, char const *const * argv)
{
  NARG();
  
  int PAGESIZE = atoi(NARG());
  int CHIPSIZE = atoi(NARG());
  int SPISPEED = atoi(NARG());
  
  char const* cmd = NARG();
  
  assure(((frob = MPSSE(SPI0, SPISPEED, MSB)) != NULL));
  assure((frob->open == 1));
  
  /****/ if (strcmp(cmd, "status") == 0) {
    fprintf(stderr, "st1=0x%02X\n", status1());
  } else if (strcmp(cmd, "read") == 0) {
    char buf[PAGESIZE];
    FILE *const f = fopen("out.bin", "wb");
    assure((f != NULL));
    ENTER();
    TX(0x03);
    TX(0x00);
    TX(0x00);
    TX(0x00);
    for (int i = 0; i < (CHIPSIZE/PAGESIZE); i++) {
      assure((FastRead(frob, buf, PAGESIZE) == MPSSE_OK));
      fwrite(buf, PAGESIZE, 1, f);
    }
    LEAVE();
    fclose(f);
  } else if (strcmp(cmd, "erase") == 0) {
    chiperase();
  } else if (strcmp(cmd, "flash") == 0) {
    char buf[PAGESIZE];
    FILE *const f = fopen("inp.bin", "rb");
    assure((f != NULL));
    {
      fprintf(stderr, "chiperase\n");
      chiperase();
    }
    {
      assure((CHIPSIZE == ((CHIPSIZE/PAGESIZE)*PAGESIZE)));
      for (int i = 0; i < (CHIPSIZE/PAGESIZE); i++) {
	int a = (i*PAGESIZE);
	assure((fread(buf, PAGESIZE, 1, f) == 1));
	if (!(allfs(buf, PAGESIZE))) {
	  fprintf(stderr, "programming address: 0x%08X\n", a);
	  writeenable();
	  ENTER();
	  TX(0x02);
	  TX((a >> 16));
	  TX((a >>  8));
	  TX((a      ));
	  assure((FastWrite(frob, buf, PAGESIZE) == MPSSE_OK));
	  LEAVE();
	  waitready();
	} else {
	  fprintf(stderr, "skipping address: 0x%08X\n", a);
	}
      }
    }
    fclose(f);
  } else if (strcmp(cmd, "mxrdid") == 0) {
    ENTER();
    TX(0x9F);
    int i1 = RXIU();
    int i2 = RXIU();
    int i3 = RXIU();
    LEAVE();
    fprintf(stderr, "rdid: [1]=%d [2]=%d [3]=%d\n", i1, i2, i3);
  } else {
    fprintf(stderr, "command not recognized\n");
    assure(0);
  }
  
  return 0;
}
